﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine.SceneControl;
//#if PROF
using PloobsEngine.DataAquisition;
//#endif
using System.Threading;
using Bnoerj.AI.Steering;
using PloobsEngine.Components;
using PloobsEngine.Factories;
using System.Diagnostics;
using PloobsEngine.Commands;
using PloobsEngine.Material;
using Microsoft.Xna.Framework.Audio;

namespace PloobsEngine
{
    public enum EngineProcessingState
    {
        Initializing , LoadingContent ,Update, DeferredDraw , DepthPass , NormalDraw
    }

    public class EngineStuff : Game
    {
        #region Delegation
        public delegate void DelegateEngineDrawAfterAll(GameTime gameTime);
        public delegate void DelegateEngineDrawWithDepth(GameTime gameTime);
        public delegate void DelegateEngineUpdate(GameTime gameTime);
        public delegate void DelegateEngineUnloadContent();
        public delegate void DelegateEngineInitialize();
        public delegate void DelegateEngineLoadContent();
        public delegate void DelegateOnExit();

        private DelegateEngineDrawAfterAll _engineDraw;
        private DelegateEngineUpdate _engineUpdate;
        private DelegateEngineUnloadContent _engineUnloadContent;
        private DelegateEngineInitialize _engineIntialize;
        private DelegateEngineLoadContent _engineLoadContent;
        private DelegateEngineDrawWithDepth _delegateEngineDrawWithDepth = null;
        private DelegateOnExit _delegateOnExit = null;

        #endregion

        private static GraphicsDeviceManager graphics;
        private static SpriteBatch spriteBatch;
        private static bool isInitialized = false;        
        private static ScreenManager _sm;
        private static ComponentManager _cm;
        private static InternalContentManager _icm;
        private static Manager _man;
        private static GameTime _gameTime;
        private static IContentManager _cmanager;
        //private static bool _isCleanEachFrameDebugVectors = true;
        private static Color BackgroundColor = Color.CornflowerBlue;
        private static float antiAliasLevel = 1f;
        private static Vector2 halfPixel;
        private static EngineProcessingState state;
        private static bool useSyncronizeVerticalRetrace;

        internal static void setCurrentEngineProcessingState(EngineProcessingState st)
        {
            state = st;
        }
        
        public static EngineProcessingState CurrentEngineProcessingState
        {
            get
            {
                return state;
            }
        }

        //#if PROF

        private static DataColector colector = new DataColector();

        public static DataColector DataColector
        {
            get { return EngineStuff.colector; }
            set { EngineStuff.colector = value; }
        }

        public static GraphicsDevice GraphicsDevice
        {
            get
            {
                return graphics.GraphicsDevice;
            }
        }

        //#endif


        public float AntiAliasLevel
        {
            get { return EngineStuff.antiAliasLevel; }
            set { EngineStuff.antiAliasLevel = value; }
        }

        /// <summary>
        /// Inicia a Engine
        /// </summary>
        /// <returns></returns>
        public static EngineStuff CreateAndStartEngine()
        {            
            return CreateAndStartEngine(true);
        }

        #region Para Colocar em um Form
        void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
                e.GraphicsDeviceInformation.PresentationParameters.DeviceWindowHandle = drawSurface;                                

               
        }
        private IntPtr drawSurface;
        public void EmbeddedInForm(IntPtr drawSurface)
        {
            this.drawSurface = drawSurface;
            graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(graphics_PreparingDeviceSettings);
            System.Windows.Forms.Control.FromHandle((this.Window.Handle)).VisibleChanged +=
            new EventHandler(Game1_VisibleChanged);
        }


        private void Game1_VisibleChanged(object sender, EventArgs e)
        {
                if (System.Windows.Forms.Control.FromHandle((this.Window.Handle)).Visible == true)
                    System.Windows.Forms.Control.FromHandle((this.Window.Handle)).Visible = false;
        }

        #endregion

        /// <summary>
        /// Inicia a Engine
        /// </summary>
        /// <param name="CanCreateClassWithoutService">Permite o GetManagedObject Funcionar com classes sem o Attributo Service</param>
        /// <param name="useAttribulesConfsFiles">usar o arquivo syncronousattributeInfo.txt para escolher quais attributos PROCURAR ao inves do procedimento automatico que mapeia TODOS os attributos</param>
        /// <returns></returns>
        public static EngineStuff CreateAndStartEngine(bool UseSyncronizeVerticalRetrace)
        {
            useSyncronizeVerticalRetrace = UseSyncronizeVerticalRetrace;
            state = EngineProcessingState.Initializing;
            #if !XBOX360    
            Debug.Listeners.Add(new TextWriterTraceListener("debug.log"));
            #endif            

            isInitialized = true;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            
            EngineStuff es = new EngineStuff();
            _cm = new ComponentManager(es);
            _sm = new ScreenManager(es);                        
            _man = new Manager(es);
            _cmanager = new CustomContentManager(es);
            _icm = new InternalContentManager(es);
            
            return es;
            

        }
        
        public static DepthStencilBuffer GetDefaultDepthStencil(int width, int height, DepthFormat format)
        {
            return new DepthStencilBuffer(graphics.GraphicsDevice, width, height, format, graphics.GraphicsDevice.PresentationParameters.MultiSampleType, graphics.GraphicsDevice.PresentationParameters.MultiSampleQuality);
        }

        public static DepthStencilBuffer GetDefaultDepthStencil()
        {
            int backBufferWidth = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            //return new DepthStencilBuffer(graphics.GraphicsDevice, 1920, 1080, DepthFormat.Depth24Stencil8);
            return new DepthStencilBuffer(graphics.GraphicsDevice, (int)(backBufferWidth * antiAliasLevel), (int)(backBufferHeight * antiAliasLevel), DepthFormat.Depth24Stencil8);
        }

        public static RenderTarget2D GetDefaultColorBuffer(int width,int height )
        {
            return new RenderTarget2D(graphics.GraphicsDevice, width, height, 1, SurfaceFormat.Color, graphics.GraphicsDevice.PresentationParameters.MultiSampleType, graphics.GraphicsDevice.PresentationParameters.MultiSampleQuality, RenderTargetUsage.PreserveContents);            
        }

        public static RenderTarget2D GetDefaultDepthBuffer()
        {
            int backBufferWidth = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            //return new RenderTarget2D(graphics.GraphicsDevice, 1920,1080, 1, SurfaceFormat.Single);
            return new RenderTarget2D(graphics.GraphicsDevice, (int)(backBufferWidth * antiAliasLevel), (int)(backBufferHeight * antiAliasLevel), 1, SurfaceFormat.Single, RenderTargetUsage.PreserveContents);
        }

        public static RenderTarget2D GetDefaultDepthBuffer(int width, int height)
        {
            int backBufferWidth = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            //return new RenderTarget2D(graphics.GraphicsDevice, 1920,1080, 1, SurfaceFormat.Single);
            return new RenderTarget2D(graphics.GraphicsDevice, (int)(width * antiAliasLevel), (int)(height * antiAliasLevel), 1, SurfaceFormat.Single, RenderTargetUsage.PreserveContents);
        }

        public static RenderTarget2D GetDefaultBuffer(int width , int height, SurfaceFormat format)
        {
            return new RenderTarget2D(graphics.GraphicsDevice, width, height, 1, format, RenderTargetUsage.PreserveContents);
        }

        public static RenderTarget2D GetDefaultBuffer(SurfaceFormat format)
        {
            int backBufferWidth = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            return new RenderTarget2D(graphics.GraphicsDevice, (int)(backBufferWidth * antiAliasLevel), (int)(backBufferHeight * antiAliasLevel), 1, format, RenderTargetUsage.PreserveContents);
        }

        public static RenderTarget2D GetDefaultColorBuffer()
        {
            int backBufferWidth = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int backBufferHeight = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            //return new RenderTarget2D(graphics.GraphicsDevice, backBufferWidth,backBufferHeight, 1, SurfaceFormat.Color,MultiSampleType.FourSamples,graphics.GraphicsDevice.PresentationParameters.MultiSampleQuality);
            return new RenderTarget2D(graphics.GraphicsDevice, (int)(backBufferWidth * antiAliasLevel), (int)(backBufferHeight * antiAliasLevel), 1, SurfaceFormat.Color, RenderTargetUsage.PreserveContents);
        }

        public static RenderTarget2D GetDefaultVector4Buffer()
        {
            PresentationParameters pp = graphics.GraphicsDevice.PresentationParameters;
            int backBufferWidth = pp.BackBufferWidth;
            int backBufferHeight = pp.BackBufferHeight;
            return new RenderTarget2D(graphics.GraphicsDevice, (int)(backBufferWidth * antiAliasLevel), (int)(backBufferHeight * antiAliasLevel), 1, SurfaceFormat.Vector4, pp.MultiSampleType, pp.MultiSampleQuality, RenderTargetUsage.PreserveContents);
        }

        public static float SoundEffectDistanceScale        
        {
            set
            {
                SoundEffect.DistanceScale = value;
            }
            get
            {
                return SoundEffect.DistanceScale;
            }
        }
        public static float SoundEffectMasterVolume
        {
            set
            {
                SoundEffect.MasterVolume = value;
            }
            get
            {
                return SoundEffect.MasterVolume;
            }
        }

        public static Vector2 HalfPixel
        {
            get
            {
                return halfPixel;
            }
            set
            {
                halfPixel = value;
            }
        }

        public static Rectangle GetFullScreenRectangle()
        {
            return new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height);
        }

        public static int BackBufferWidth
        {
            get
            {
                return (int)(graphics.GraphicsDevice.PresentationParameters.BackBufferWidth * antiAliasLevel);
                //return 1920;
            }
        }

        public static int BackBufferHeight
        {
            get
            {
                return (int)(graphics.GraphicsDevice.PresentationParameters.BackBufferHeight * antiAliasLevel);
                //return 1080;
            }
        }

        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {            
    #if !XBOX
            Exception ex = e.ExceptionObject as Exception;
            Debug.WriteLine(DateTime.Now.ToLocalTime());
            Debug.WriteLine(ex.ToString());
            Debug.WriteLine("--");
            Debug.Flush();
    #endif
            
        }
        public EngineStuff()
        {
            if (isInitialized==false)
            {
                throw new Exception("Inicializa usando o metodo estatico CreateAndStartEngine");
            }
            //Tem q tar aqui, pq se nao ele acha q o LoadContent ja foi chamado !!!
            graphics = new GraphicsDeviceManager(this);
            graphics.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(PrepareGraphicsDevice);

            Content.RootDirectory = "Content";

        }
        
        public void AddComponent(IComponent comp)
        {
            _cm.AddComponent(comp);
            
        }
        public bool HasComponentAdded(String name)
        {
            return _cm.IsActive(name);
        }

        public void RemoveComponent(String name)
        {
            _cm.RemoveComponent(name);
        }

        public void SetResolution(int width, int height)
        {            

            graphics.PreferredBackBufferWidth = width;
            graphics.PreferredBackBufferHeight = height;          

            graphics.ApplyChanges();

        }

        public static IContentManager CustomContentManager
        {
            get
            {
                return _cmanager;
            }
        }

        internal static IContentManager InternalContentManager
        {
            get
            {
                return _icm;
            }
        }    

        public void TogleFullScreen()
        {
            graphics.ToggleFullScreen();            
        }
        public static GameTime GetCurrentGameTime()
        {
            return _gameTime;
        }

        protected override void Initialize()
        {                                    
            _cm.Initialize();
            _sm.Initialize();
            if (_engineIntialize != null)
               _engineIntialize();
            base.Initialize();
        }

        protected override void OnExiting(object sender, EventArgs args)
        {
            if (_delegateOnExit != null)
                _delegateOnExit();
            base.OnExiting(sender, args);
        }

        protected  override void LoadContent()
        {
            //graphics.SynchronizeWithVerticalRetrace = useSyncronizeVerticalRetrace;
            //graphics.ApplyChanges();

            ImpostorsBank.Initiate(this);
            state = EngineProcessingState.LoadingContent;
            halfPixel.X = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferWidth;
            halfPixel.Y = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferHeight;
            halfPixel *= -antiAliasLevel;
            spriteBatch = new SpriteBatch(GraphicsDevice);
            _cm.LoadContent(this);
            _sm.LoadContent();
            if (_engineLoadContent != null)
            _engineLoadContent();
            ScreenManager.AfterLoadContent();
            base.LoadContent();
        }

        protected override void Draw(GameTime gameTime)
        {
            _gameTime = gameTime;        

            state = EngineProcessingState.NormalDraw;
            PushPhase(Phase.Draw);
            EngineStuff.DataColector.Recieve(new MethodDataFormat(MethodPart.START, "Draw"));
            GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, BackGroundColor, 1.0f, 0);
            
            base.Draw(gameTime);    
            this.ScreenManager.Draw(gameTime);

            
            EngineStuff.DataColector.Recieve(new MethodDataFormat(MethodPart.END, "Draw"));
            PopPhase();

        }
        
        protected override void Update(GameTime gameTime)
        {

            state = EngineProcessingState.Update;
            clock.Update();

            //  start the phase timer (XXX to accurately measure "overhead" time this
            //  should be in displayFunc, or somehow account for time outside this
            //  routine)
            InitPhaseTimers();

            // switch to Update phase
            PushPhase(Phase.Update);

            EngineStuff.DataColector.Recieve(new MethodDataFormat(MethodPart.START, "Update"));

            _gameTime = gameTime;
            this.IsMouseVisible = true;
            //if(_isCleanEachFrameDebugVectors)
            //DebugVectorDraw.ClearVectorList();
            _cm.Update(gameTime);
            CommandProcessor.getCommandProcessor().ProcessCommands();
            this.ScreenManager.Update(gameTime);
            if (_engineUpdate != null)
            _engineUpdate(gameTime);
            base.Update(gameTime);

            EngineStuff.DataColector.Recieve(new MethodDataFormat(MethodPart.END, "Update"));

            PopPhase();
        }
        
        protected override void UnloadContent()
        {
            if (_engineUnloadContent != null)
            _engineUnloadContent();
            base.UnloadContent();
        }

        internal void PreRender(GameTime gt, Matrix activeView, Matrix activeProjection)
        {
            _cm.PreDraw(gt,activeView,activeProjection);
        }

        internal void PosRender(GameTime gt, Matrix activeView, Matrix activeProjection)
        {
            if (_engineDraw != null)
                _engineDraw(gt);

            _cm.AfterDraw(gt,activeView,activeProjection);
        }

        internal void PosRenderWithDepth(GameTime gt)
        {
            if(EngineDrawWithDepth!=null)
            EngineDrawWithDepth(gt);
        }
        
        #region Properties
        public DelegateEngineDrawAfterAll EngineDrawAfterAll
        {
            set
            {
                _engineDraw += value;
            }
            get
            {
                return _engineDraw;
            }
        }
        public DelegateEngineDrawWithDepth EngineDrawWithDepth
        {
            get { return _delegateEngineDrawWithDepth; }
            set { _delegateEngineDrawWithDepth += value; }
        }
        public DelegateEngineUpdate EngineUpdate
        {
            set
            {
                _engineUpdate += value;
            }
            get
            {
                return _engineUpdate;
            }
        }

        public static Viewport Viewport
        {
            get
            {
                return graphics.GraphicsDevice.Viewport;
            }
            set
            {
                graphics.GraphicsDevice.Viewport = value;
            }
        }


        public DelegateOnExit EngineOnExit
        {
            set
            {
                this._delegateOnExit += value;
            }
            get
            {
                return _delegateOnExit;
            }
        }

        public DelegateEngineUnloadContent EngineUnloadContent
        {
            set
            {
                _engineUnloadContent += value;
            }
            get
            {
                   return _engineUnloadContent;
            }
        }
        public DelegateEngineInitialize EngineInitialize
        {
            set
            {
                _engineIntialize += value;
            }
            get
            {
                return _engineIntialize;
            }
        }
        public DelegateEngineLoadContent EngineLoadContent
        {
            set
            {
                _engineLoadContent += value;
            }
            get
            {
                return _engineLoadContent;
            }
        }

        public ScreenManager ScreenManager
        {
            get
            {
                return _sm;
            }

        }

        public GraphicsDeviceManager GraphicsDeviceManager
        {
            get
            {
                return graphics;
            }

        }

        public static SpriteBatch SpriteBatch
        {
            get
            {
                return spriteBatch;
            }

        }

        public Manager Manager
        {
            get
            {
                return _man;
            }

        }

        public Game Game
        {
            get
            {
                return this;
            }
        }

        public Color BackGroundColor
        {
            set
            {
                BackgroundColor = value;
            }
            get
            {
                return BackgroundColor;
            }
        }

                
        protected virtual void PrepareGraphicsDevice(object sender, PreparingDeviceSettingsEventArgs e)
        {
            e.GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage = RenderTargetUsage.PreserveContents;
            foreach (GraphicsAdapter adapter in GraphicsAdapter.Adapters)
            {
                if (adapter.Description.Contains("PerfHUD"))
                {
                    e.GraphicsDeviceInformation.Adapter = adapter;
                    e.GraphicsDeviceInformation.DeviceType = DeviceType.Reference;
                    break;
                }
            }
            graphics.SynchronizeWithVerticalRetrace = useSyncronizeVerticalRetrace;
        }

        #endregion

        private static Clock clock = new Clock();

        public static Clock Clock
        {
                get
                {
                    return clock;
                }
        }      




        void PushPhase(Phase newPhase)
        {
            // update timer for current (old) phase: add in time since last switch
            UpdatePhaseTimers();

            // save old phase
            phaseStack[phaseStackIndex++] = phase;

            // set new phase
            phase = newPhase;

            // check for stack overflow
            if (phaseStackIndex >= phaseStackSize)
            {
                throw new ArgumentOutOfRangeException("phaseStack overflow");
            }
        }

        void PopPhase()
        {
            // update timer for current (old) phase: add in time since last switch
            UpdatePhaseTimers();

            // restore old phase
            phase = phaseStack[--phaseStackIndex];
        }


        public enum Phase
        {
            Overhead,
            Update,
            Draw,
            Count
        }
        static Phase phase;
        const int phaseStackSize = 5;
        static Phase[] phaseStack = new Phase[phaseStackSize];
        static int phaseStackIndex = 0;
        static float[] phaseTimers = new float[(int)Phase.Count];
        static float phaseTimerBase = 0;

        void InitPhaseTimers()
        {
            phaseTimers[(int)Phase.Draw] = 0;
            phaseTimers[(int)Phase.Update] = 0;
            phaseTimers[(int)Phase.Overhead] = 0;
            phaseTimerBase = clock.TotalRealTime;
        }

        void UpdatePhaseTimers()
        {
            float currentRealTime = clock.RealTimeSinceFirstClockUpdate();
            phaseTimers[(int)phase] += currentRealTime - phaseTimerBase;
            phaseTimerBase = currentRealTime;
        }






     }
    
}
